/*!
 * \file WTSSessionInfo.hpp
 * \project	WonderTrader
 *
 * \author Wesley
 * \date 2020/03/30
 * 
 * \brief WonderTrader交易时段模块定义文件
 * 
 * \details 本文件定义了WonderTrader框架中交易时段管理的核心类：
 *          - 交易时段信息：管理各个品种的交易时间段
 *          - 时间转换：提供时间与分钟数之间的转换功能
 *          - 时段判断：判断指定时间是否在交易时段内
 *          - 偏移处理：支持跨日交易的时间偏移计算
 *          
 *          该类是交易系统中时间管理的基础组件，为策略执行、
 *          风控监控、数据处理等模块提供准确的时间判断服务。
 */
#pragma once
#include <vector>

#include "WTSObject.hpp"
#include "../Share/TimeUtils.hpp"

NS_WTP_BEGIN

static const char* DEFAULT_SESSIONID = "TRADING";	//默认交易时段标识

/*!
 * \brief 交易时段信息类
 * 
 * \details 管理单个品种或市场的交易时段信息，提供以下核心功能：
 *          - 交易时段管理：添加和管理多个交易时间段
 *          - 集合竞价时段：设置和管理集合竞价时间
 *          - 时间转换：时间格式与分钟数/秒数之间的转换
 *          - 时段判断：判断指定时间是否在交易时段或集合竞价时段
 *          - 偏移处理：支持夜盘等跨日交易的时间偏移
 *          
 *          该类特别适用于期货、股票等不同交易品种的时段管理，
 *          支持复杂的交易时间安排和跨日交易场景。
 * 
 * \note 时间格式统一使用HHMM格式（如0930表示9:30）
 * 
 * \author Wesley
 * \date 2020/03/30
 */
class WTSSessionInfo : public WTSObject
{
public:
	//交易时段类型定义
	typedef std::pair<uint32_t, uint32_t>	TradingSection;		//交易时段：开始时间-结束时间对
	typedef std::vector<TradingSection>		TradingTimes;		//交易时段集合

protected:
	TradingTimes	m_tradingTimes;		//交易时段列表
	TradingSection	m_auctionTime;		//集合竞价时段
	int32_t			m_uOffsetMins;		//时间偏移分钟数（用于跨日交易）

	std::string		m_strID;			//时段标识ID
	std::string		m_strName;			//时段名称

protected:
	/*!
	 * \brief 受保护的构造函数
	 * 
	 * \param offset 时间偏移分钟数，用于处理跨日交易
	 * 
	 * \details 防止直接实例化，只能通过create()静态方法创建
	 */
	WTSSessionInfo(int32_t offset)
	{
		m_uOffsetMins = offset;
	}
	
	/*!
	 * \brief 虚析构函数
	 * 
	 * \details 确保派生类能够正确析构
	 */
	virtual ~WTSSessionInfo(){}

public:
	/*!
	 * \brief 获取时段标识ID
	 * \return 时段标识字符串
	 */
	const char* id() const{ return m_strID.c_str(); }
	
	/*!
	 * \brief 获取时段名称
	 * \return 时段名称字符串
	 */
	const char* name() const{ return m_strName.c_str(); }

	/*!
	 * \brief 创建交易时段信息对象
	 * 
	 * \param sid 时段标识ID
	 * \param name 时段名称
	 * \param offset 时间偏移分钟数，默认为0
	 * \return 新创建的WTSSessionInfo对象指针
	 * 
	 * \details 静态工厂方法，创建并初始化交易时段信息对象
	 */
	static WTSSessionInfo* create(const char* sid, const char* name, int32_t offset = 0)
	{
		WTSSessionInfo* pRet = new WTSSessionInfo(offset);
		pRet->m_strID = sid;
		pRet->m_strName = name;
		return pRet;
	}

public:
	/*!
	 * \brief 获取时间偏移分钟数
	 * \return 偏移分钟数
	 * 
	 * \details 用于跨日交易的时间计算，负数表示向前偏移
	 */
	int32_t	getOffsetMins() const{return m_uOffsetMins;}

	/*!
	 * \brief 添加交易时段
	 * 
	 * \param sTime 开始时间（HHMM格式）
	 * \param eTime 结束时间（HHMM格式）
	 * 
	 * \details 添加一个新的交易时间段，会自动应用时间偏移
	 */
	void addTradingSection(uint32_t sTime, uint32_t eTime)
	{
		sTime = offsetTime(sTime, true);
		eTime = offsetTime(eTime, false);
		m_tradingTimes.emplace_back(TradingSection(sTime, eTime));
	}

	/*!
	 * \brief 设置集合竞价时段
	 * 
	 * \param sTime 集合竞价开始时间（HHMM格式）
	 * \param eTime 集合竞价结束时间（HHMM格式）
	 * 
	 * \details 设置集合竞价的时间段，会自动应用时间偏移
	 */
	void setAuctionTime(uint32_t sTime, uint32_t eTime)
	{
		m_auctionTime.first = offsetTime(sTime, true);
		m_auctionTime.second = offsetTime(eTime, false);
	}

	/*!
	 * \brief 设置时间偏移分钟数
	 * \param offset 偏移分钟数
	 */
	void setOffsetMins(int32_t offset){m_uOffsetMins = offset;}

	/*!
	 * \brief 获取交易时段列表
	 * \return 交易时段集合的常量引用
	 */
	const TradingTimes& getTradingSections() const{ return m_tradingTimes; }
	
	/*!
	 * \brief 获取集合竞价时段
	 * \return 集合竞价时段的常量引用
	 */
	const TradingSection&	getAuctionSection() const{ return m_auctionTime; }

	//需要输出给脚本的函数
public:
	/*!
	 * \brief 获取交易时段数量
	 * \return 交易时段的数量
	 */
	uint32_t getSectionCount() const{ return (uint32_t)m_tradingTimes.size(); }

	/*!
	 * \brief 获取偏移后的日期
	 * 
	 * \param uDate 日期，默认为0（使用当前日期）
	 * \param uTime 时间，默认为0（使用当前时间）
	 * \return 偏移后的日期
	 * 
	 * \details 主要用于夜盘偏移，在跨日期比较时使用。
	 *          夜盘的偏移会在第二天的某个时间点后才会变化。
	 */
	uint32_t getOffsetDate(uint32_t uDate = 0, uint32_t uTime = 0)
	{
		if(uDate == 0)
		{
			TimeUtils::getDateTime(uDate, uTime);
			uTime /= 100000;
		}

		int32_t curMinute = (uTime / 100) * 60 + uTime % 100;
		curMinute += m_uOffsetMins;

		if (curMinute >= 1440)
			return TimeUtils::getNextDate(uDate);

		if (curMinute < 0)
			return TimeUtils::getNextDate(uDate, -1);

		return uDate;
	}

	/*!
	 * \brief 将时间转换成分钟数
	 * 
	 * \param uTime 当前时间，格式如0910
	 * \param autoAdjust 是否自动调整。如果为真，是将非交易时间内的时间，
	 *                   自动调整到下一个交易时间，如8点59分的委托，会自动调整到9点00分的委托。
	 *                   这不会有币种影响，但是时无法确定，需要记得的非交易时间内收到的委托都被调整了。
	 *                   如果有交易时间内的，应该没有问题。
	 * \return 从交易开始到指定时间的分钟数，如果不在交易时间内返回INVALID_UINT32
	 * 
	 * \details 将HHMM格式的时间转换为从交易开始算起的分钟数，
	 *          主要用于K线数据的时间索引和策略计算。
	 */
	uint32_t timeToMinutes(uint32_t uTime, bool autoAdjust = false)
	{
		if(m_tradingTimes.empty())
			return INVALID_UINT32;

		if(isInAuctionTime(uTime))
			return 0;

		uint32_t offTime = offsetTime(uTime, true);

		uint32_t offset = 0;
		bool bFound = false;
		auto it = m_tradingTimes.begin();
		for(; it != m_tradingTimes.end(); it++)
		{
			TradingSection &section = *it;
			if (section.first <= offTime && offTime <= section.second)
			{
				int32_t hour = offTime / 100 - section.first / 100;
				int32_t minute = offTime % 100 - section.first % 100;
				offset += hour*60 + minute;
				bFound = true;
				break;
			}
			else if(offTime > section.second)	//超过上边界
			{
				int32_t hour = section.second/100 - section.first/100;
				int32_t minute = section.second%100 - section.first%100;
				offset += hour*60 + minute;
				//offset += section.second - section.first;
			} 
			else //小于下边界
			{
				if(autoAdjust)
				{
					bFound = true;
				}
				break;
			}
		}

		//没找到就返回0
		if(!bFound)
			return INVALID_UINT32;

		return offset;
	}

	/*!
	 * \brief 将分钟数转换成时间
	 * 
	 * \param uMinutes 从交易开始算起的分钟数
	 * \param bHeadFirst 是否头部优先，默认为false
	 * \return HHMM格式的时间，如果超出范围返回INVALID_UINT32
	 * 
	 * \details 将从交易开始算起的分钟数转换为具体的HHMM格式时间，
	 *          与timeToMinutes()方法互为逆操作。
	 */
	uint32_t minuteToTime(uint32_t uMinutes, bool bHeadFirst = false)
	{
		if(m_tradingTimes.empty())
			return INVALID_UINT32;

		uint32_t offset = uMinutes;
		TradingTimes::iterator it = m_tradingTimes.begin();
		for(; it != m_tradingTimes.end(); it++)
		{
			TradingSection &section = *it;
			uint32_t startMin = section.first/100*60 + section.first%100;
			uint32_t stopMin = section.second/100*60 + section.second%100;

			if(!bHeadFirst)
			{
				if (startMin + offset >= stopMin)
				{
					offset -= (stopMin - startMin);
					if (offset == 0)
					{
						return originalTime(stopMin / 60 * 100 + stopMin % 60);
					}
				}
				else
				{
					//目标位于该时段
					uint32_t desMin = startMin + offset;
					if (desMin >= 1440)
						desMin -= 1440;

					return originalTime(desMin / 60 * 100 + desMin % 60);
				}
			}
			else
			{
				if (startMin + offset < stopMin)
				{
					//目标位于该时段
					uint32_t desMin = startMin + offset;
					if (desMin >= 1440)
						desMin -= 1440;

					return originalTime(desMin / 60 * 100 + desMin % 60);
				}
				else
				{
					offset -= (stopMin - startMin);
				}
			}
		}

		return INVALID_UINT32;
	}

	/*!
	 * \brief 将时间转换成秒数
	 * 
	 * \param uTime HHMM格式的时间
	 * \return 从交易开始到指定时间的秒数，如果不在交易时间内返回INVALID_UINT32
	 * 
	 * \details 类似于timeToMinutes()，但返回的是秒数，精度更高
	 */
	uint32_t timeToSeconds(uint32_t uTime)
	{
		if(m_tradingTimes.empty())
			return INVALID_UINT32;

		if(isInAuctionTime(uTime))
			return 0;

		uint32_t offTime = offsetTime(uTime/100, true)*100 + uTime%100;

		uint32_t offset = 0;
		bool bFound = false;
		auto it = m_tradingTimes.begin();
		for(; it != m_tradingTimes.end(); it++)
		{
			TradingSection &section = *it;
			uint32_t sTime = section.first*100;
			uint32_t eTime = section.second*100;
			if (sTime <= offTime && offTime <= eTime)
			{
				uint32_t sHour = sTime/10000;
				uint32_t sMin = (sTime%10000)/100;
				uint32_t sSec = sTime%100;

				uint32_t eHour = offTime/10000;
				uint32_t eMin = (offTime%10000)/100;
				uint32_t eSec = offTime%100;

				offset += (eHour-sHour)*3600 + (eMin-sMin)*60 + (eSec-sSec);
				bFound = true;
				break;
			}
			else if(offTime > eTime)	//超过上边界
			{
				uint32_t sHour = sTime/10000;
				uint32_t sMin = (sTime%10000)/100;
				uint32_t sSec = sTime%100;

				uint32_t eHour = eTime/10000;
				uint32_t eMin = (eTime%10000)/100;
				uint32_t eSec = eTime%100;

				offset += (eHour-sHour)*3600 + (eMin-sMin)*60 + (eSec-sSec);
			} 
			else //小于下边界
			{
				break;
			}
		}

		//没找到就返回0
		if(!bFound)
			return INVALID_UINT32;

		return offset;
	}

	/*!
	 * \brief 将秒数转换成时间
	 * 
	 * \param seconds 从交易开始算起的秒数
	 * \return HHMMSS格式的时间，如果超出范围返回INVALID_UINT32
	 * 
	 * \details 将从交易开始算起的秒数转换为具体的HHMMSS格式时间，
	 *          与timeToSeconds()方法互为逆操作。
	 */
	uint32_t secondsToTime(uint32_t seconds)
	{
		if(m_tradingTimes.empty())
			return INVALID_UINT32;

		uint32_t offset = seconds;
		TradingTimes::iterator it = m_tradingTimes.begin();
		for(; it != m_tradingTimes.end(); it++)
		{
			TradingSection &section = *it;
			uint32_t startSecs = (section.first/100)*3600 + (section.first%100)*60;
			uint32_t stopSecs = (section.second/100)*3600 + (section.second%100)*60;

			if(startSecs + offset >= stopSecs)
			{
				offset -= (stopSecs - startSecs);
				if(offset == 0)
				{
					uint32_t desMin = stopSecs/60;
					return originalTime((desMin/60*100 + desMin%60))*100 + stopSecs%60;
				}
			}
			else
			{
				//目标位于该时段
				uint32_t desSecs = startSecs+offset;
				if(desSecs >= 86400)
					desSecs -= 86400;

				uint32_t desMin = desSecs/60;
				return originalTime((desMin/60*100 + desMin%60))*100 + desSecs%60;
			}
		}

		return INVALID_UINT32;
	}

	/*!
	 * \brief 获取开盘时间
	 * 
	 * \param bOffseted 是否返回偏移后的时间，默认为false
	 * \return 开盘时间（HHMM格式）
	 * 
	 * \details 返回第一个交易时段的开始时间
	 */
	inline uint32_t getOpenTime(bool bOffseted = false) const
	{
		if(m_tradingTimes.empty())
			return 0;

		if(bOffseted)
			return m_tradingTimes[0].first;
		else
			return originalTime(m_tradingTimes[0].first);
	}

	/*!
	 * \brief 获取集合竞价开始时间
	 * 
	 * \param bOffseted 是否返回偏移后的时间，默认为false
	 * \return 集合竞价开始时间（HHMM格式），如果没有设置返回-1
	 * 
	 * \details 返回集合竞价时段的开始时间
	 */
	inline uint32_t getAuctionStartTime(bool bOffseted = false) const
	{
		if(m_auctionTime.first == 0 && m_auctionTime.second == 0)
			return -1;

		if(bOffseted)
			return m_auctionTime.first;
		else
			return originalTime(m_auctionTime.first);
	}

	/*!
	 * \brief 获取收盘时间
	 * 
	 * \param bOffseted 是否返回偏移后的时间，默认为false
	 * \return 收盘时间（HHMM格式）
	 * 
	 * \details 返回最后一个交易时段的结束时间
	 */
	inline uint32_t getCloseTime(bool bOffseted = false) const
	{
		if(m_tradingTimes.empty())
			return 0;

		uint32_t ret = 0;
		if(bOffseted)
			ret = m_tradingTimes[m_tradingTimes.size()-1].second;
		else
			ret = originalTime(m_tradingTimes[m_tradingTimes.size()-1].second);

		// By Wesley @ 2021.12.25
		// 如果收盘时间是0点，无法和开盘时间进行比较，所以这里要加一天的分钟数
		if (ret == 0 && bOffseted)
			ret = 2400;

		return ret;
	}

	/*!
	 * \brief 获取总交易秒数
	 * 
	 * \return 当日总交易秒数
	 * 
	 * \details 计算所有交易时段的总秒数，用于统计和分析
	 */
	inline uint32_t getTradingSeconds()
	{
		uint32_t count = 0;
		TradingTimes::iterator it = m_tradingTimes.begin();
		for(; it != m_tradingTimes.end(); it++)
		{
			TradingSection &section = *it;
			uint32_t s = section.first;
			uint32_t e = section.second;

			uint32_t hour = (e/100 - s/100);
			uint32_t minute = (e%100 - s%100);
			count += hour*60+minute;
		}

		//By Welsey @ 2021.12.25
		//如果没有设置全天候交易时间
		if (count == 0) count = 1440;
		return count*60;
	}

	/*!
	 * \brief 获取总交易分钟数
	 * 
	 * \return 当日总交易分钟数
	 * 
	 * \details 计算所有交易时段的总分钟数，用于统计和分析
	 */
	inline uint32_t getTradingMins()
	{
		uint32_t count = 0;
		TradingTimes::iterator it = m_tradingTimes.begin();
		for (; it != m_tradingTimes.end(); it++)
		{
			TradingSection &section = *it;
			uint32_t s = section.first;
			uint32_t e = section.second;

			uint32_t hour = (e / 100 - s / 100);
			uint32_t minute = (e % 100 - s % 100);
			count += hour * 60 + minute;
		}
		//By Welsey @ 2021.12.25
		//如果没有设置全天候交易时间
		if (count == 0) count = 1440;
		return count;
	}

	/*!
	 * \brief 判断是否在交易时间内
	 * 
	 * \param uTime 时间，格式为HHMM
	 * \param bStrict 是否严格检查，如果严格检查，
	 *                则每一个交易时段的最后一分钟，如1500不算在交易时间内
	 * \return true表示在交易时间内，false表示不在交易时间内
	 * 
	 * \details 判断指定时间是否在任一交易时段内，支持严格模式检查
	 */
	bool	isInTradingTime(uint32_t uTime, bool bStrict = false)
	{
		uint32_t count = timeToMinutes(uTime);
		if(count == INVALID_UINT32)
			return false;

		if (bStrict && isLastOfSection(uTime))
			return false;

		return true;
	}

	/*!
	 * \brief 判断是否为时段的最后时刻
	 * 
	 * \param uTime 时间（HHMM格式）
	 * \return true表示是某个时段的结束时间，false表示不是
	 * 
	 * \details 检查指定时间是否恰好是某个交易时段的结束时间
	 */
	inline bool	isLastOfSection(uint32_t uTime)
	{
		uint32_t offTime = offsetTime(uTime, false);
		TradingTimes::iterator it = m_tradingTimes.begin();
		for(; it != m_tradingTimes.end(); it++)
		{
			TradingSection &section = *it;
			if(section.second == offTime)
				return true;
		}

		return false;
	}

	/*!
	 * \brief 判断是否为时段的第一时刻
	 * 
	 * \param uTime 时间（HHMM格式）
	 * \return true表示是某个时段的开始时间，false表示不是
	 * 
	 * \details 检查指定时间是否恰好是某个交易时段的开始时间
	 */
	inline bool	isFirstOfSection(uint32_t uTime)
	{
		uint32_t offTime = offsetTime(uTime, true);
		TradingTimes::iterator it = m_tradingTimes.begin();
		for(; it != m_tradingTimes.end(); it++)
		{
			TradingSection &section = *it;
			if(section.first == offTime)
				return true;
		}

		return false;
	}

	/*!
	 * \brief 判断是否在集合竞价时间内
	 * 
	 * \param uTime 时间（HHMM格式）
	 * \return true表示在集合竞价时间内，false表示不在
	 * 
	 * \details 检查指定时间是否在集合竞价时段内
	 */
	inline bool	isInAuctionTime(uint32_t uTime)
	{
		uint32_t offTime = offsetTime(uTime, true);

		if(m_auctionTime.first == 0 && m_auctionTime.second == 0)
			return false;

		if (m_auctionTime.first <= offTime && offTime < m_auctionTime.second)
			return true;

		return false;
	}

	/*!
	 * \brief 获取交易时段列表
	 * \return 交易时段集合的常量引用
	 */
	const TradingTimes &getTradingTimes() const{return m_tradingTimes;}

	/*!
	 * \brief 对时间进行偏移处理
	 * 
	 * \param uTime 原始时间（HHMM格式）
	 * \param bAlignLeft 是否左对齐
	 * \return 偏移后的时间（HHMM格式）
	 * 
	 * \details 根据设置的偏移分钟数对时间进行调整，主要用于跨日交易处理
	 */
	inline uint32_t	offsetTime(uint32_t uTime, bool bAlignLeft) const
	{
		int32_t curMinute = (uTime/100)*60 + uTime%100;
		curMinute += m_uOffsetMins;
		if(bAlignLeft)
		{
			if (curMinute >= 1440)
				curMinute -= 1440;
			else if (curMinute < 0)
				curMinute += 1440;
		}
		else
		{
			if (curMinute > 1440)
				curMinute -= 1440;
			else if (curMinute <= 0)
				curMinute += 1440;
		}
		
		return (curMinute/60)*100 + curMinute%60;

		return uTime;
	}

	/*!
	 * \brief 将偏移时间还原为原始时间
	 * 
	 * \param uTime 偏移后的时间（HHMM格式）
	 * \return 原始时间（HHMM格式）
	 * 
	 * \details 与offsetTime()方法互为逆操作，将偏移后的时间还原为原始时间
	 */
	inline uint32_t	originalTime(uint32_t uTime) const
	{
		int32_t curMinute = (uTime/100)*60 + uTime%100;
		curMinute -= m_uOffsetMins;
		if(curMinute >= 1440)
			curMinute -= 1440;
		else if(curMinute < 0)
			curMinute += 1440;

		return (curMinute/60)*100 + curMinute%60;
	}
};

NS_WTP_END